#pragma once 

#include <vector>
#include <ostream>

extern "C" {
	#include "../cvm_c/cvm.h"
	#include "../cvm_c/cvm_extra.h"
}

namespace cvm
{
	struct TokenStream;

	std::ostream& operator<<(std::ostream& os, Val* x);
	std::ostream& operator<<(std::ostream& os, VM& vm);

	struct VM {
		::VM* vm;
		VM() { vm = new ::VM(); vm_init(vm); }
		~VM() { vm_free(vm); delete vm; }
		Val* operator[](int n) { return At(n); }
		Val* Top() { return At(0); }
		bool Empty() { return Count() == 0; }
		bool AuxEmpty() { return AuxCount() == 0; }
		Val* At(int n) { return vm_at(n); }
		Val* AuxAt(int n) { return vm_aux_at(n); }
		int Count() { return vm_count(); }
		int AuxCount() { return vm_aux_count(); }
		bool Next() { return vm_next(vm) == 0; } 
	};

	struct Token {			
		virtual Token* Clone() const = 0;
		template<typename T> bool Is() { return As<T>() != NULL; }
		template<typename T> T* As() { return dynamic_cast<T*>(this); } 
	};

	template<typename T>
	struct Push : Token {
		T value;
		Push(const T& x) : value(x) { }
		virtual Token* Clone() const { return new Push<T>(value); }
	};

	struct Op : Token {
		const char* name;
		byte opcode;
		static int OpCodeWidth(byte opcode) { return vm_op_width(opcode); }
		Op(const Op& op) : opcode(op.opcode), name(op.name) { }
		Op(byte _opcode) : opcode(_opcode), name(OpCodeName(_opcode)) { }
		
		virtual Token* Clone() const {
			return new Op(*this);
		}
	};
	
	struct OpArg : Token {
		byte n;
		OpArg(byte _n) : n(_n) { }
		virtual Token* Clone() const { return new OpArg(n); }
	};

	struct Define {
		TokenStream* stream;
		Define(TokenStream* _stream) : stream(_stream) { }
		~Define();
		size_t Count();
		Token* At(size_t i);
	};

	struct Function : Token {
		Define* define;
		Function(Define* _define) : define(_define) { }
		virtual Token* Clone() const {
			return new Function(define); 
		}
	};

	struct TokenStream {
		std::vector<Token*> vec;
		TokenStream() : vec() { }
		TokenStream(const TokenStream& str) : vec(str.vec) { }
		~TokenStream() { for (size_t i = 0; i < vec.size(); ++i) delete vec[i]; }
		VM& operator()(VM& vm) { }		
		TokenStream* Add(Token* t) { vec.push_back(t); return this; }
	};

	TokenStream* operator>(TokenStream* a, const Token& b);
	TokenStream* operator>(TokenStream* a, Define& b);
	TokenStream* Begin();
	Push<int> int_(int x);
	OpArg* arg_(int x);

	#define OPCODE(OP, WIDTH, CAT, DESC) static Op OP##_ (_##OP);
	#include "../cvm_c/opcodes.h"
}